# main.py

import sys
import threading
import time
import wx

from ftp_client import FTP
from ObjectListView import ObjectListView, ColumnDefn
from pubsub import pub

def send_status(message, topic='update_status'):
    wx.CallAfter(pub.sendMessage,
                 topic,
                 message=message)


class FtpPanel(wx.Panel):

    def __init__(self, parent):
        super().__init__(parent)
        self.frame = parent

        self.ftp = None
        self.paths = []

        self.main_sizer = wx.BoxSizer(wx.VERTICAL)
        self.create_ui()
        self.SetSizer(self.main_sizer)
        pub.subscribe(self.update, 'update')
        pub.subscribe(self.update_status, 'update_status')

    def create_ui(self):
        size = (150, -1)
        connect_sizer = wx.BoxSizer()
        # host, username, password, port, connect button
        host_lbl = wx.StaticText(self, label='Host:')
        connect_sizer.Add(host_lbl, 0, wx.ALL | wx.CENTER, 5)
        self.host = wx.TextCtrl(self, size=size)
        connect_sizer.Add(self.host, 0, wx.ALL, 5)

        user_lbl = wx.StaticText(self, label='Username:')
        connect_sizer.Add(user_lbl, 0, wx.ALL | wx.CENTER, 5)
        self.user = wx.TextCtrl(self, size=size)
        connect_sizer.Add(self.user, 0, wx.ALL, 5)

        password_lbl = wx.StaticText(self, label='Password:')
        connect_sizer.Add(password_lbl, 0, wx.ALL | wx.CENTER, 5)
        self.password = wx.TextCtrl(self, size=size, style=wx.TE_PASSWORD)
        connect_sizer.Add(self.password, 0, wx.ALL, 5)

        port_lbl = wx.StaticText(self, label='Port:')
        connect_sizer.Add(port_lbl, 0, wx.ALL | wx.CENTER, 5)
        self.port = wx.TextCtrl(self, value='21', size=(50, -1))
        connect_sizer.Add(self.port, 0, wx.ALL, 5)

        connect_btn = wx.Button(self, label='Connect')
        connect_btn.Bind(wx.EVT_BUTTON, self.on_connect)
        connect_sizer.Add(connect_btn, 0, wx.ALL, 5)
        self.main_sizer.Add(connect_sizer)

        self.status = wx.TextCtrl(self, style=wx.TE_MULTILINE)
        self.main_sizer.Add(self.status, 1, wx.ALL | wx.EXPAND, 5)

        folder_ico = wx.ArtProvider.GetBitmap(
            wx.ART_FOLDER, wx.ART_TOOLBAR, (16, 16))
        file_ico = wx.ArtProvider.GetBitmap(
            wx.ART_HELP_PAGE, wx.ART_TOOLBAR, (16, 16))

        self.remote_server = ObjectListView(
            self, style=wx.LC_REPORT | wx.SUNKEN_BORDER)
        self.remote_server.Bind(wx.EVT_LIST_ITEM_ACTIVATED,
                                self.on_change_directory)
        self.remote_server.AddNamedImages('folder', smallImage=folder_ico)
        self.remote_server.AddNamedImages('file', smallImage=file_ico)
        self.remote_server.SetEmptyListMsg("Not Connected")
        self.main_sizer.Add(self.remote_server, 2, wx.ALL | wx.EXPAND, 5)
        self.update_ui()

    def on_connect(self, event):
        host = self.host.GetValue()
        username = self.user.GetValue()
        password = self.password.GetValue()
        port = int(self.port.GetValue())
        self.frame.SetStatusText('Connecting...', 1)

        if host and username and password and port:
            self.ftp = FTP()
            try:
                self.ftp.disconnect()
            except:
                pass
            args = [self.ftp, host, port, username, password]
            self.thread = threading.Thread(
                target=self.connect_thread, args=args)
            self.thread.daemon = True
            self.thread.start()

    def change_dir_thread(self, ftp, folder):
        """
        This method should only be run using a thread
        """
        ftp.change_directory(folder)

    def connect_thread(self, ftp, host, port, username, password):
        """
        This method should only be run using a thread
        """
        ftp.connect(host, port, username, password)

    def image_getter(self, path):
        """
        Helper method to determine which image to use in
        the ObjectListView widget
        """
        if path.folder:
            return "folder"
        else:
            return "file"

    def on_change_directory(self, event):
        current_selection = self.remote_server.GetSelectedObject()
        if current_selection.folder:
            self.thread = threading.Thread(
                target=self.change_dir_thread,
                args=[self.ftp, current_selection.filename])
            self.thread.daemon = True
            self.thread.start()

    def update(self, paths):
        """
        Called by pubsub / thread
        """
        self.paths = paths
        self.update_ui()

    def update_status(self, message):
        """
        Called by pubsub / thread
        """
        ts = time.strftime(time.strftime('%H:%M:%S',
                                         time.gmtime(time.time()
                                                     )
                                         )
                           )
        if '\n' in message:
            for line in message.split('\n'):
                line = f'{ts} {line}'
                self.status.WriteText(f'{line}\n')
        else:
            message = f'{ts} {message}'
            self.status.WriteText(f'{message}\n')

    def update_ui(self):
        self.remote_server.SetColumns([
            ColumnDefn("File/Folder", "left", 800, "filename",
                       imageGetter=self.image_getter),
            ColumnDefn("Filesize", "right", 80, "size"),
            ColumnDefn("Last Modified", "left", 150, "last_modified")
        ])
        self.remote_server.SetObjects(self.paths)


class FtpFrame(wx.Frame):

    def __init__(self):
        super().__init__(None, title='PythonFTP', size=(1200, 600))
        self.panel = FtpPanel(self)
        self.create_toolbar()
        self.statusbar = self.CreateStatusBar(2)
        self.statusbar.SetStatusText('Disconnected', 1)
        pub.subscribe(self.update_statusbar, 'update_statusbar')
        self.Show()

    def create_toolbar(self):
        self.toolbar = self.CreateToolBar()

        add_ico = wx.ArtProvider.GetBitmap(
            wx.ART_GO_UP, wx.ART_TOOLBAR, (16, 16))
        add_file_tool = self.toolbar.AddTool(
            wx.ID_ANY, 'Upload File', add_ico,
            'Upload a file')
        self.Bind(wx.EVT_MENU, self.on_upload_file,
                  add_file_tool)

        add_ico = wx.ArtProvider.GetBitmap(
            wx.ART_GO_DOWN, wx.ART_TOOLBAR, (16, 16))
        add_file_tool = self.toolbar.AddTool(
            wx.ID_ANY, 'Download File', add_ico,
            'Download a file')
        self.Bind(wx.EVT_MENU, self.on_download_file,
                  add_file_tool)

        remove_ico = wx.ArtProvider.GetBitmap(
            wx.ART_MINUS, wx.ART_TOOLBAR, (16, 16))
        remove_tool = self.toolbar.AddTool(
            wx.ID_ANY, 'Remove File', remove_ico,
            'Remove file')
        self.Bind(wx.EVT_MENU, self.on_remove, remove_tool)

        self.toolbar.Realize()

    def on_upload_file(self, event):
        if self.statusbar.GetStatusText(1) != 'Connected':
            return

        paths = None
        with wx.FileDialog(
            self, message="Choose a file",
            defaultDir='~',
            defaultFile="",
            wildcard="All files (*.*)|*.*",
            style=wx.FD_OPEN | wx.FD_MULTIPLE
            ) as dlg:
            if dlg.ShowModal() == wx.ID_OK:
                paths = dlg.GetPaths()
        if paths:
            self.thread = threading.Thread(
                target=self.panel.ftp.upload_files,
                args=[paths])
            self.thread.daemon = True
            self.thread.start()

    def on_download_file(self, event):
        if self.statusbar.GetStatusText(1) != 'Connected':
            return

        local_folder = None
        selections = self.panel.remote_server.GetSelectedObjects()
        if not selections:
            return

        with wx.DirDialog(
            self, "Choose a directory:",
            style=wx.DD_DEFAULT_STYLE,
            defaultPath='~') as dlg:
            if dlg.ShowModal() == wx.ID_OK:
                local_folder = dlg.GetPath()
        if local_folder and selections:
            # Filter out folder selections
            paths = [path.filename for path in selections
                     if not path.folder]
            self.thread = threading.Thread(
                target=self.panel.ftp.download_files,
                args=[paths, local_folder])
            self.thread.daemon = True
            self.thread.start()

    def on_remove(self, event):
        selection = self.panel.remote_server.GetSelectedObject()
        if not selection:
            return

        # Ask user if they really want to delete the file
        with wx.MessageDialog(
            parent=None,
            message=f'Do you really want to delete {selection}?',
            caption='Confirmation',
            style=wx.OK | wx.CANCEL | wx.ICON_QUESTION) as dlg:
            if dlg.ShowModal() == wx.ID_OK:
                self.thread = threading.Thread(
                    target=self.panel.ftp.delete_file,
                    args=[selection.filename])
                self.thread.daemon = True
                self.thread.start()

    def update_statusbar(self, message):
        self.SetStatusText(message, 1)


if __name__ == '__main__':
    app = wx.App(False)
    frame = FtpFrame()
    app.MainLoop()